// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package testing

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"sync/atomic"
	"time"
)

func initFuzzFlags() {
	matchFuzz = flag.String("test.fuzz", "", "run the fuzz test matching `regexp`")
	flag.Var(&fuzzDuration, "test.fuzztime", "time to spend fuzzing; default is to run indefinitely")
	flag.Var(&minimizeDuration, "test.fuzzminimizetime", "time to spend minimizing a value after finding a failing input")

	fuzzCacheDir = flag.String("test.fuzzcachedir", "", "directory where interesting fuzzing inputs are stored (for use only by cmd/go)")
	isFuzzWorker = flag.Bool("test.fuzzworker", false, "coordinate with the parent process to fuzz random values (for use only by cmd/go)")
}

var (
	matchFuzz        *string
	fuzzDuration     durationOrCountFlag
	minimizeDuration = durationOrCountFlag{d: 60 * time.Second, allowZero: true}
	fuzzCacheDir     *string
	isFuzzWorker     *bool

	// corpusDir是包中模糊测试种子库的父目录。
	corpusDir = "testdata/fuzz"
)

// 在发生内部错误后，fuzzWorkerExitCode被fuzz worker进程用作退出代码。这将内部错误与失控的恐慌
// 和其他失败区分开来。与内部/模糊保持同步。WorkerExit代码。
const fuzzWorkerExitCode = 70

// InternalFuzzTarget是一个内部类型，但由于它是
// 交叉包而被导出；它是“go test”命令实现的一部分。
type InternalFuzzTarget struct {
	Name string
	Fn   func(f *F)
}

// F是传递给模糊测试的类型。
// 
// 模糊测试针对提供的模糊目标运行生成的输入，该目标可以发现并报告正在测试的代码中的潜在错误。
// 
// 默认情况下，模糊测试会运行种子库，其中包括
// by（*F）提供的条目。在testdata/fuzz/<FuzzTestName>目录中添加和项。在
// 任何必要的设置和调用（*F）之后。另外，模糊测试必须调用
// /（*F）。Fuzz提供模糊目标。有关示例，请参见测试包文档
// 以及
// 的F.Fuzz和F.Add方法文档。
// 
// /*F方法只能在（*F）之前调用。绒毛。一旦测试是
// 执行模糊目标，就只能使用（*T）方法。在（*F）中允许使用的唯一*F方法
// 。模糊函数是（*F）。失败和（*F）。名称
type F struct {
	common
	fuzzContext *fuzzContext
	testContext *testContext

	// inFuzzFn在fuzz函数运行时为true。当inFuzzFn为true时，大多数F方法都不能调用
	// 。
	inFuzzFn bool

	// 语料库是一组种子语料库条目，添加了F。添加并从testdata加载
	// 。
	corpus []corpusEntry

	result     fuzzResult
	fuzzCalled bool
}

var _ TB = (*F)(nil)

// corpusEntry是与internal/fuzz相同类型的别名。小体进入。
// 我们使用一个类型别名，因为我们不想导出这个类型，而且我们不能从测试中导入内部/模糊。
type corpusEntry = struct {
	Parent     string
	Path       string
	Data       []byte
	Values     []any
	Generation int
	IsSeed     bool
}

// Helper将调用函数标记为测试助手函数。
// 打印文件和行信息时，该功能将被跳过。
// 可以从多个goroutine同时调用Helper。
func (f *F) Helper() {
	if f.inFuzzFn {
		panic("testing: f.Helper was called inside the fuzz target, use t.Helper instead")
	}

	// 常见。Helper在这里是内联的。
	// 如果我们调用它，它会将F.Helper标记为Helper 
	// 而不是调用者。
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.helperPCs == nil {
		f.helperPCs = make(map[uintptr]struct{})
	}
	// 在此处重复callerName中的代码以避免遍历堆栈帧
	var pc [1]uintptr
	n := runtime.Callers(2, pc[:]) // 跳过运行时。调用方+帮助方
	if n == 0 {
		panic("testing: zero callers found")
	}
	if _, found := f.helperPCs[pc[0]]; !found {
		f.helperPCs[pc[0]] = struct{}{}
		f.helperNames = nil // 下次需要时将重新创建映射
	}
}

// 失败将函数标记为已失败，但将继续执行。
func (f *F) Fail() {
	// /（*F）。Fail可由（*T）调用。失败，我们应该允许。然而，我们不应该允许直接（*F）。来自（*F）内部的呼叫失败。模糊函数。
	if f.inFuzzFn {
		panic("testing: f.Fail was called inside the fuzz target, use t.Fail instead")
	}
	f.common.Helper()
	f.common.Fail()
}

// 跳过报告是否跳过了测试。
func (f *F) Skipped() bool {
	// /（*F）。tRunner可能会叫Skipped，这是我们应该允许的。然而，我们不应该允许直接（*F）。跳过了来自（*F）内部的呼叫。模糊函数。
	if f.inFuzzFn {
		panic("testing: f.Skipped was called inside the fuzz target, use t.Skipped instead")
	}
	f.common.Helper()
	return f.common.Skipped()
}

// Add将把参数添加到模糊测试的种子语料库中。如果在fuzz目标之后或内部调用，这将是
// 一个no op，并且args必须与fuzz目标的
// 参数匹配。
func (f *F) Add(args ...any) {
	var values []any
	for i := range args {
		if t := reflect.TypeOf(args[i]); !supportedTypes[t] {
			panic(fmt.Sprintf("testing: unsupported type to Add %v", t))
		}
		values = append(values, args[i])
	}
	f.corpus = append(f.corpus, corpusEntry{Values: values, IsSeed: true, Path: fmt.Sprintf("seed#%d", len(f.corpus))})
}

// supportedTypes表示所有支持的类型，这些类型可以被模糊化。
var supportedTypes = map[reflect.Type]bool{
	reflect.TypeOf(([]byte)("")):  true,
	reflect.TypeOf((string)("")):  true,
	reflect.TypeOf((bool)(false)): true,
	reflect.TypeOf((byte)(0)):     true,
	reflect.TypeOf((rune)(0)):     true,
	reflect.TypeOf((float32)(0)):  true,
	reflect.TypeOf((float64)(0)):  true,
	reflect.TypeOf((int)(0)):      true,
	reflect.TypeOf((int8)(0)):     true,
	reflect.TypeOf((int16)(0)):    true,
	reflect.TypeOf((int32)(0)):    true,
	reflect.TypeOf((int64)(0)):    true,
	reflect.TypeOf((uint)(0)):     true,
	reflect.TypeOf((uint8)(0)):    true,
	reflect.TypeOf((uint16)(0)):   true,
	reflect.TypeOf((uint32)(0)):   true,
	reflect.TypeOf((uint64)(0)):   true,
}

// Fuzz运行Fuzz函数ff进行模糊测试。如果ff对一组
// 参数失败，这些参数将被添加到种子库中。
// 
// ff必须是一个没有返回值的函数，其第一个参数是*T，而
// 其剩余参数是要模糊化的类型。
// 例如：
// 
// f.Fuzz（func（t*testing.t，b[]字节，i int）{…}）
// 
// 允许以下类型：[]字节、字符串、布尔、字节、符文、float32、
// float64、int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint34。
// 将来可能会支持更多类型。
// 
// ff不能调用任何*F方法，例如（*F）。对数，（*F）。错误，（*F）。跳过使用
// 相应的*T方法。
// 中唯一允许的*F方法（*F）。模糊函数是（*F）。失败和（*F）。名称
// 
// 此函数应快速且具有确定性，其行为不应是
// 取决于共享状态。在执行fuzz函数期间，不应保留任何可更改的输入参数或指向它们的指针，因为支持它们的内存
// 可能会在后续调用期间更改。ff不能修改模糊引擎提供的参数的基础数据。
// 
// 当模糊化时，F.Fuzz不会返回，直到发现问题、时间耗尽
// （设置为-fuzztime），或者测试过程被信号中断。F.Fuzz 
// 应该只调用一次，除非事先调用了F.Skip或F.Fail。
func (f *F) Fuzz(ff any) {
	if f.fuzzCalled {
		panic("testing: F.Fuzz called more than once")
	}
	f.fuzzCalled = true
	if f.failed {
		return
	}
	f.Helper()

	// ff的格式应为func（*testing.T，…interface{}）
	fn := reflect.ValueOf(ff)
	fnType := fn.Type()
	if fnType.Kind() != reflect.Func {
		panic("testing: F.Fuzz must receive a function")
	}
	if fnType.NumIn() < 2 || fnType.In(0) != reflect.TypeOf((*T)(nil)) {
		panic("testing: fuzz target must receive at least two arguments, where the first argument is a *T")
	}
	if fnType.NumOut() != 0 {
		panic("testing: fuzz target must not return a value")
	}

	// 保存函数类型以与语料库进行比较。
	var types []reflect.Type
	for i := 1; i < fnType.NumIn(); i++ {
		t := fnType.In(i)
		if !supportedTypes[t] {
			panic(fmt.Sprintf("testing: unsupported type for fuzzing %v", t))
		}
		types = append(types, t)
	}

	// 加载testdata种子库。检查testdata 
	// 语料库中的条目类型以及用F.Add声明的条目。
	// 
	// 如果这是一个工作进程，则不要加载种子库；我们不会用的。
	if f.fuzzContext.mode != fuzzWorker {
		for _, c := range f.corpus {
			if err := f.fuzzContext.deps.CheckCorpus(c.Values, types); err != nil {
				// TODO（#48302）：报告F.Add调用的源位置。
				f.Fatal(err)
			}
		}

		// 加载种子库
		c, err := f.fuzzContext.deps.ReadCorpus(filepath.Join(corpusDir, f.name), types)
		if err != nil {
			f.Fatal(err)
		}
		for i := range c {
			c[i].IsSeed = true // 这些都是种子库值
			if f.fuzzContext.mode == fuzzCoordinator {
				// 如果这是协调进程，请将这些值归零，因为我们不需要
				// 来保留它们。
				c[i].Values = nil
			}
		}

		f.corpus = append(f.corpus, c...)
	}

	// run在给定的输入上调用fn，作为一个有自己的T的子测试。
	// run类似于T.run。测试过滤和清理的工作原理类似。
	// fn在它自己的goroutine中被称为。
	run := func(captureOut io.Writer, e corpusEntry) (ok bool) {
		if e.Values == nil {
			// 要运行
			// 测试，该微粒项必须具有非零值。如果值为nil，则是代码中的错误。
			panic(fmt.Sprintf("corpus file %q was not unmarshaled", e.Path))
		}
		if shouldFailFast() {
			return true
		}
		testName := f.name
		if e.Path != "" {
			testName = fmt.Sprintf("%s/%s", testName, filepath.Base(e.Path))
		}
		if f.testContext.isFuzzing {
			// 模糊时不要保留子测试名称。如果fn调用T.Run，
			// 将有大量名称重复的子测试，这将占用大量内存。这些子测试名称没有用，因为无法确定地重新运行它们。
			f.testContext.match.clearSubNames()
		}

		// 记录此调用点的堆栈跟踪，这样，如果在单独堆栈中运行的子测试
		// 函数被标记为帮助程序，我们可以
		// 继续将堆栈遍历到父测试中。
		var pc [maxStackLen]uintptr
		n := runtime.Callers(2, pc[:])
		t := &T{
			common: common{
				barrier: make(chan bool),
				signal:  make(chan bool),
				name:    testName,
				parent:  &f.common,
				level:   f.level + 1,
				creator: pc[:n],
				chatty:  f.chatty,
			},
			context: f.testContext,
		}
		if captureOut != nil {
			// t.父别名f.公共。
			t.parent.w = captureOut
		}
		t.w = indenter{&t.common}
		if t.chatty != nil {
			// TODO（#48132）：将其调整为与test2json一起使用。
			t.chatty.Updatef(t.name, "=== RUN   %s\n", t.name)
		}
		f.common.inFuzzFn, f.inFuzzFn = true, true
		go tRunner(t, func(t *T) {
			args := []reflect.Value{reflect.ValueOf(t)}
			for _, v := range e.Values {
				args = append(args, reflect.ValueOf(v))
			}
			// 在重置当前覆盖范围之前，请延迟快照，以便
			// 我们确保在tRunner函数
			// 退出之前调用它，无论它是否被干净、惊慌、
			// 或者fuzzFn是否调用了t.Fatal。
			if f.testContext.isFuzzing {
				defer f.fuzzContext.deps.SnapshotCoverage()
				f.fuzzContext.deps.ResetCoverage()
			}
			fn.Call(args)
		})
		<-t.signal
		f.common.inFuzzFn, f.inFuzzFn = false, false
		return !t.Failed()
	}

	switch f.fuzzContext.mode {
	case fuzzCoordinator:
		// 启用了模糊化，这是由“go test”启动的测试过程。
		// 充当协调程序，并协调工人执行
		// 实际模糊化。
		corpusTargetDir := filepath.Join(corpusDir, f.name)
		cacheTargetDir := filepath.Join(*fuzzCacheDir, f.name)
		err := f.fuzzContext.deps.CoordinateFuzzing(
			fuzzDuration.d,
			int64(fuzzDuration.n),
			minimizeDuration.d,
			int64(minimizeDuration.n),
			*parallel,
			f.corpus,
			types,
			corpusTargetDir,
			cacheTargetDir)
		if err != nil {
			f.result = fuzzResult{Error: err}
			f.Fail()
			fmt.Fprintf(f.w, "%v\n", err)
			if crashErr, ok := err.(fuzzCrashError); ok {
				crashPath := crashErr.CrashPath()
				fmt.Fprintf(f.w, "Failing input written to %s\n", crashPath)
				testName := filepath.Base(crashPath)
				fmt.Fprintf(f.w, "To re-run:\ngo test -run=%s/%s\n", f.name, testName)
			}
		}
		// TODO（jayconrod，katiehockman）：工人间的汇总统计
		// 并添加到FuzzResult（即所用时间，迭代次数）

	case fuzzWorker:
		// 已启用模糊化，这是一个工人流程。按照协调员的指示进行操作。
		if err := f.fuzzContext.deps.RunFuzzWorker(func(e corpusEntry) error {
			// 如果从
			// fuzz worker运行，请不要向f.w（指向Stdout）写入。这将变得非常冗长，尤其是在
			// 最小化期间。返回错误，让调用方处理
			// 输出。
			var buf bytes.Buffer
			if ok := run(&buf, e); !ok {
				return errors.New(buf.String())
			}
			return nil
		}); err != nil {
			// 内部错误标记为f.失败；在F.Fuzz之前，用户代码可能也会调用它。
			// 工作程序将带着FuzzWorkerExit代码退出，表明这是一个失败
			// （并且“go test”应退出非零），但不应记录失败的输入。
			f.Errorf("communicating with fuzzing coordinator: %v", err)
		}

	default:
		// 未启用模糊化，或将在以后执行。现在只运行种子
		// 语料库。
		for _, e := range f.corpus {
			name := fmt.Sprintf("%s/%s", f.name, filepath.Base(e.Path))
			if _, ok, _ := f.testContext.match.fullName(nil, name); ok {
				run(f.w, e)
			}
		}
	}
}

func (f *F) report() {
	if *isFuzzWorker || f.parent == nil {
		return
	}
	dstr := fmtDuration(f.duration)
	format := "--- %s: %s (%s)\n"
	if f.Failed() {
		f.flushToParent(f.name, format, "FAIL", f.name, dstr)
	} else if f.chatty != nil {
		if f.Skipped() {
			f.flushToParent(f.name, format, "SKIP", f.name, dstr)
		} else {
			f.flushToParent(f.name, format, "PASS", f.name, dstr)
		}
	}
}

// fuzzResult包含fuzz运行的结果。
type fuzzResult struct {
	N     int           // 迭代次数。
	T     time.Duration // 所用的总时间。
	Error error         // Error是来自失败输入的错误
}

func (r fuzzResult) String() string {
	if r.Error == nil {
		return ""
	}
	return r.Error.Error()
}

// fuzzCrashError是在模糊化过程中检测到的失败输入。
// 这些错误被写入种子库，可以使用“go test”重新运行。
// 模糊框架中的错误（如协调器
// 和工作进程之间的I/O错误）不满足此接口。
type fuzzCrashError interface {
	error
	Unwrap() error

	// CrashPath返回子测试的路径，该路径对应于种子库中保存的
	// crash输入文件。可以使用go test 
	// /-run=$test/$name$test是模糊测试名称，$name是
	// filepath重新运行测试。这里返回的字符串的底部。
	CrashPath() string
}

// fuzzContext包含所有fuzz测试通用的字段。
type fuzzContext struct {
	deps testDeps
	mode fuzzMode
}

type fuzzMode uint8

const (
	seedCorpusOnly fuzzMode = iota
	fuzzCoordinator
	fuzzWorker
)

// runFuzzTests运行与-run模式匹配的模糊测试。这将
// 只运行（*F）。不使用
// 模糊引擎生成或变异输入，为每个种子语料库提供模糊函数。
func runFuzzTests(deps testDeps, fuzzTests []InternalFuzzTarget, deadline time.Time) (ran, ok bool) {
	ok = true
	if len(fuzzTests) == 0 || *isFuzzWorker {
		return ran, ok
	}
	m := newMatcher(deps.MatchString, *match, "-test.run")
	tctx := newTestContext(*parallel, m)
	tctx.deadline = deadline
	var mFuzz *matcher
	if *matchFuzz != "" {
		mFuzz = newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz")
	}
	fctx := &fuzzContext{deps: deps, mode: seedCorpusOnly}
	root := common{w: os.Stdout} // 在一个地方收集输出
	if Verbose() {
		root.chatty = newChattyPrinter(root.w)
	}
	for _, ft := range fuzzTests {
		if shouldFailFast() {
			break
		}
		testName, matched, _ := tctx.match.fullName(nil, ft.Name)
		if !matched {
			continue
		}
		if mFuzz != nil {
			if _, fuzzMatched, _ := mFuzz.fullName(nil, ft.Name); fuzzMatched {
				// 如果这将被模糊化，那么现在不要运行种子库
				// 了。这将在以后发生。
				continue
			}
		}
		f := &F{
			common: common{
				signal:  make(chan bool),
				barrier: make(chan bool),
				name:    testName,
				parent:  &root,
				level:   root.level + 1,
				chatty:  root.chatty,
			},
			testContext: tctx,
			fuzzContext: fctx,
		}
		f.w = indenter{&f.common}
		if f.chatty != nil {
			// TODO（#48132）：将其调整为与test2json一起使用。
			f.chatty.Updatef(f.name, "=== RUN   %s\n", f.name)
		}

		go fRunner(f, ft.Fn)
		<-f.signal
	}
	return root.ran, !root.Failed()
}

// runFuzzing运行与-fuzz模式匹配的fuzz测试。只有一个这样的
// 模糊测试必须匹配。这将运行模糊引擎生成并
// 针对模糊目标变异新输入。
// 
// 如果禁用了模糊功能（-test.fuzz未设置），运行模糊功能
// 将立即返回。
func runFuzzing(deps testDeps, fuzzTests []InternalFuzzTarget) (ok bool) {
	if len(fuzzTests) == 0 || *matchFuzz == "" {
		return true
	}
	m := newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz")
	tctx := newTestContext(1, m)
	tctx.isFuzzing = true
	fctx := &fuzzContext{
		deps: deps,
	}
	root := common{w: os.Stdout}
	if *isFuzzWorker {
		root.w = io.Discard
		fctx.mode = fuzzWorker
	} else {
		fctx.mode = fuzzCoordinator
	}
	if Verbose() && !*isFuzzWorker {
		root.chatty = newChattyPrinter(root.w)
	}
	var fuzzTest *InternalFuzzTarget
	var testName string
	var matched []string
	for i := range fuzzTests {
		name, ok, _ := tctx.match.fullName(nil, fuzzTests[i].Name)
		if !ok {
			continue
		}
		matched = append(matched, name)
		fuzzTest = &fuzzTests[i]
		testName = name
	}
	if len(matched) == 0 {
		fmt.Fprintln(os.Stderr, "testing: warning: no fuzz tests to fuzz")
		return true
	}
	if len(matched) > 1 {
		fmt.Fprintf(os.Stderr, "testing: will not fuzz, -fuzz matches more than one fuzz test: %v\n", matched)
		return false
	}

	f := &F{
		common: common{
			signal:  make(chan bool),
			barrier: nil, // T.并行在模糊化时没有效果。
			name:    testName,
			parent:  &root,
			level:   root.level + 1,
			chatty:  root.chatty,
		},
		fuzzContext: fctx,
		testContext: tctx,
	}
	f.w = indenter{&f.common}
	if f.chatty != nil {
		// TODO（#48132）：将其调整为与test2json一起使用。
		f.chatty.Updatef(f.name, "=== FUZZ  %s\n", f.name)
	}
	go fRunner(f, fuzzTest.Fn)
	<-f.signal
	return !f.failed
}

// fRunner包装对模糊测试的调用，并确保调用了清理函数
// 并设置了状态标志。fRunner应该在自己的
// goroutine中被称为fRunner。要等待其完成，请接收来自f的信号。
// 
// fRunner类似于tRunner，它包装了从T.Run开始的子测试。
// 单元测试和模糊测试的工作方式稍有不同，因此目前，这些
// 函数尚未合并。特别是，由于没有F.Run和
// F.并行方法，即没有模糊子测试或并行模糊测试，因此对
// 进行了一些简化。我们还要求调用F.Fuzz、F.Skip或F.Fail。
func fRunner(f *F, fn func(*F)) {
	// 当这个goroutine完成时，或者是因为运行时。Goexit被调用，
	// 恐慌开始，或fn正常返回，记录持续时间并发送
	// t.信号，表明模糊测试已完成。
	defer func() {
		// 检测模糊测试是否惊慌失措或调用了运行时。Goexit 
		// 不调用F.Fuzz、F.Fail或F.Skip。如果有，则为恐慌（可能是
		// 替换为零恐慌值）。在fRunner 
		// 展开后，不会恢复任何内容，因此这会使进程和打印堆栈崩溃。
		// 不幸的是，这里的恢复添加了堆栈帧，但是
		// 的位置应该仍然是
		// 清除。
		if f.Failed() {
			atomic.AddUint32(&numFailed, 1)
		}
		err := recover()
		if err == nil {
			f.mu.RLock()
			fuzzNotCalled := !f.fuzzCalled && !f.skipped && !f.failed
			if !f.finished && !f.skipped && !f.failed {
				err = errNilPanicOrGoexit
			}
			f.mu.RUnlock()
			if fuzzNotCalled && err == nil {
				f.Error("returned without calling F.Fuzz, F.Fail, or F.Skip")
			}
		}

		// 使用延迟调用确保我们报告测试是
		// 完成的，即使清除函数调用F.FailNow。见第41355期。
		didPanic := false
		defer func() {
			if !didPanic {
				// 只有在没有死机的情况下才报告测试完成，否则测试二进制文件可以在死机报告给用户之前退出。见第41479期。
				f.signal <- true
			}
		}()

		// 如果我们恢复了恐慌或不适当的运行时间。Goexit，测试失败，
		// 将输出日志刷新到根目录，然后死机。
		doPanic := func(err any) {
			f.Fail()
			if r := f.runCleanup(recoverAndReturnPanic); r != nil {
				f.Logf("cleanup panicked with %v", r)
			}
			for root := &f.common; root.parent != nil; root = root.parent {
				root.mu.Lock()
				root.duration += time.Since(root.start)
				d := root.duration
				root.mu.Unlock()
				root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d))
			}
			didPanic = true
			panic(err)
		}
		if err != nil {
			doPanic(err)
		}

		// 没有恐慌或不当的退出。
		f.duration += time.Since(f.start)

		if len(f.sub) > 0 {
			// 在运行种子库时取消阻止调用T.Parallel的输入。
			// 这只影响作为正常测试运行的模糊测试。
			// 在模糊化时，T.Parallel没有效果，因此f.sub是空的，并且这个
			// 分支不被采用。f、 在这种情况下，屏障为零。
			f.testContext.release()
			close(f.barrier)
			// 等待子测试完成。
			for _, sub := range f.sub {
				<-sub.signal
			}
			cleanupStart := time.Now()
			err := f.runCleanup(recoverAndReturnPanic)
			f.duration += time.Since(cleanupStart)
			if err != nil {
				doPanic(err)
			}
		}

		// 完成所有子测试后报告。
		f.report()
		f.done = true
		f.setRan()
	}()
	defer func() {
		if len(f.sub) == 0 {
			f.runCleanup(normalPanic)
		}
	}()

	f.start = time.Now()
	fn(f)

	// 调用FailNow或SkipNow 
	// 时，将不会执行超出此点的代码。
	f.mu.Lock()
	f.finished = true
	f.mu.Unlock()
}
